package cn.bug.greenflag.service.impl;


import cn.bug.greenflag.ex.ServiceCode;
import cn.bug.greenflag.ex.ServiceException;
import cn.bug.greenflag.mapper.AdminRoleMapper;
import cn.bug.greenflag.mapper.RoleMapper;
import cn.bug.greenflag.pojo.dto.AdminAddNewDTO;
import cn.bug.greenflag.pojo.entity.AdminRole;
import cn.bug.greenflag.pojo.vo.AdminFaceLoginVO;
import cn.bug.greenflag.pojo.vo.AdminListItemVO;
import cn.bug.greenflag.service.IAdminService;
import cn.hutool.core.collection.CollectionUtil;
import cn.bug.greenflag.factory.FaceEngineFactory;
import cn.bug.greenflag.mapper.AdminMapper;
import cn.bug.greenflag.pojo.dto.ProcessInfo;
import cn.bug.greenflag.pojo.entity.Admin;
import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageInfo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;


/**
 * @author 我
 */
@Slf4j
@Service
public class AdminServiceImpl implements IAdminService {

    @Value("${config.arcface-sdk.sdk-lib-path}")
    public String sdkLibPath;
    @Value("${config.arcface-sdk.app-id}")
    public String appId;

    @Value("${config.arcface-sdk.sdk-key}")
    public String sdkKey;

    @Value("${config.arcface-sdk.thread-pool-size}")
    public Integer threadPoolSize;

    private ExecutorService executorService;

    private GenericObjectPool<FaceEngine> faceEngineObjectPool;

    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private AdminRoleMapper adminRoleMapper;
/*    @Autowired
    private PasswordEncoder passwordEncoder;*/
    /**
     * 预期匹配通过率
     */
    private Integer passRate = 80;

    @Override
    public void addNew(AdminAddNewDTO adminAddNewDTO) {
        log.debug("service: 开始处理添加管理员请求");
        //检查用户名是否被占用,被占用提示错误
        if (adminMapper.countByName(adminAddNewDTO.getName()) > 0) {
            // 是：抛出ServiceException（ERR_CONFLICT）
            String message = "添加管理员失败，用户名" + adminAddNewDTO.getName() + "已经被占用！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }
        //检查手机号是否被占用,被占用提示错误
        String phone = adminAddNewDTO.getPhone();
        int countPhone = adminMapper.countByPhone(phone);
        if (countPhone > 0) {
            // 是：抛出ServiceException（ERR_CONFLICT）
            String message = "添加管理员失败，手机号码【" + phone + "】已经被占用！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }
        //创建admin对象
        Admin admin = new Admin();
        //空白数值赋值

        //将用户输入的数据放在admin对象中
        BeanUtils.copyProperties(adminAddNewDTO, admin);
        //给予默认的权限enable
        admin.setEnable(1);
        //对密码进行加密
        String rowPassword = admin.getPassword();
        String encodePassword = passwordEncoder.encode(rowPassword);
        admin.setPassword(encodePassword);
        log.debug("即将插入管理员数据：{}", admin);
        //将数据写入数据库
        int row = adminMapper.addNew(admin);

        //检查是否成功
        if (row != 1) {
            String message = "服务器忙,添加管理员失败";
            log.error(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }
        //检查角色id是否与数据库的查询数量一致 不一致则报错
        Long[] roleIds = adminAddNewDTO.getRoleIds();
        //TODO 在角色表中根据参数传入的roleIds查询角色数量并且判断数量是否正确
        //检查是否有超级管理员角色,如果有就报错
        log.debug("原角色id数组：{}", Arrays.toString(roleIds));
        Arrays.sort(roleIds);
        if (roleIds[0] == 1L) {
            roleIds[0] = roleIds[roleIds.length - 1];
            roleIds = Arrays.copyOf(roleIds, roleIds.length - 1);
            System.out.println("不允许角色id有=1的id!!!!!!!!");
        }
        if (roleIds.length == 0) {
            String message = "添加角色的数量为0 , 请重新填写";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
        //插入管理员与角色的关联关系
        log.debug("准备插入管理员与角色的关联关系");
        LocalDateTime now = LocalDateTime.now();
        List<AdminRole> adminRoleList = new ArrayList<>();
        for (int i = 0; i < roleIds.length; i++) {
            AdminRole adminRole = new AdminRole();
            adminRole.setAdminId(admin.getId());
            adminRole.setRoleId(roleIds[i]);
            adminRole.setGmtCreate(now);
            adminRole.setGmtModified(now);
            adminRoleList.add(adminRole);
        }
        row = adminRoleMapper.insertBatch(adminRoleList);
        if (row != roleIds.length){
            String message = "添加管理员失败，服务器忙，请稍后再尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
        log.debug("添加管理员完成");
    }

    @Override
    public List<AdminListItemVO> list() {
        return adminMapper.list();
    }

    @Override
    public void faceAdd(ImageInfo imageInfo, Long id) {
        //人脸特征获取
        byte[] bytes = extractFaceFeature(imageInfo);
        if (bytes == null) {
            throw new RuntimeException("未检测到人脸");
        }

        // TODO: 判断id的用户是否存在
        Admin admin = new Admin();
        admin.setId(id);
        admin.setFaceFeature(bytes);

        //人脸特征插入到数据库
        Integer row = adminMapper.update(admin);
        if (row != 1) {
            throw new ServiceException(ServiceCode.ERR_UPDATE, "修改失败,网络波动,请稍后再试");
        }

    }

    @Override
    public List<AdminFaceLoginVO> compareFaceFeature(byte[] faceFeature) throws InterruptedException, ExecutionException {
        List<AdminFaceLoginVO> resultFaceInfoList = Lists.newLinkedList();//识别到的人脸列表

        FaceFeature targetFaceFeature = new FaceFeature();
        targetFaceFeature.setFeatureData(faceFeature);
        List<AdminFaceLoginVO> faceInfoList = adminMapper.getFaceLoginList(); //从数据库中取出人脸库
        List<List<AdminFaceLoginVO>> adminFaceLoginVOPartList = Lists.partition(faceInfoList, 1000);//分成1000一组，多线程处理
        CompletionService<List<AdminFaceLoginVO>> completionService = new ExecutorCompletionService(executorService);
        for (List<AdminFaceLoginVO> part : adminFaceLoginVOPartList) {
            completionService.submit(new CompareFaceTask(part, targetFaceFeature));
        }
        for (int i = 0; i < adminFaceLoginVOPartList.size(); i++) {
            List<AdminFaceLoginVO> adminFaceLoginVOList = completionService.take().get();
            if (CollectionUtil.isNotEmpty(faceInfoList)) {
                resultFaceInfoList.addAll(adminFaceLoginVOList);
            }
        }

        resultFaceInfoList.sort((h1, h2) -> h2.getSimilarValue().compareTo(h1.getSimilarValue()));//从大到小排序

        return resultFaceInfoList;
    }

    @Override
    public byte[] extractFaceFeature(ImageInfo imageInfo) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();

            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();

            //人脸检测
            int i = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);

            if (CollectionUtil.isNotEmpty(faceInfoList)) {
                FaceFeature faceFeature = new FaceFeature();
                //提取人脸特征
                faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);

                return faceFeature.getFeatureData();
            }
        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }

        }

        return null;
    }

    @Override
    public List<FaceInfo> detectFaces(ImageInfo imageInfo) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();

            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();

            //人脸检测
            faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            return faceInfoList;
        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }
        }
        return null;
    }

    @Override
    public List<ProcessInfo> process(ImageInfo imageInfo) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineObjectPool.borrowObject();
            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
            //人脸检测
            faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            int processResult = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, FunctionConfiguration.builder().supportAge(true).supportGender(true).build());
            List<ProcessInfo> processInfoList = Lists.newLinkedList();

            List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
            //性别提取
            int genderCode = faceEngine.getGender(genderInfoList);
            //年龄提取
            List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
            int ageCode = faceEngine.getAge(ageInfoList);
            for (int i = 0; i < genderInfoList.size(); i++) {
                ProcessInfo processInfo = new ProcessInfo();
                processInfo.setGender(genderInfoList.get(i).getGender());
                processInfo.setAge(ageInfoList.get(i).getAge());
                processInfoList.add(processInfo);
            }
            return processInfoList;

        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineObjectPool.returnObject(faceEngine);
            }
        }

        return null;

    }

    @PostConstruct
    public void init() {

        executorService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMaxIdle(threadPoolSize);
        poolConfig.setMaxTotal(threadPoolSize);
        poolConfig.setMinIdle(threadPoolSize);
        poolConfig.setLifo(false);

        //引擎配置
        EngineConfiguration engineConfiguration = new EngineConfiguration();
        engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);

        //功能配置
        FunctionConfiguration functionConfiguration = new FunctionConfiguration();
        functionConfiguration.setSupportAge(true);
        functionConfiguration.setSupportFace3dAngle(true);
        functionConfiguration.setSupportFaceDetect(true);
        functionConfiguration.setSupportFaceRecognition(true);
        functionConfiguration.setSupportGender(true);
        functionConfiguration.setSupportLiveness(true);
        functionConfiguration.setSupportIRLiveness(true);
        engineConfiguration.setFunctionConfiguration(functionConfiguration);

//底层库算法对象池
        faceEngineObjectPool = new GenericObjectPool(new FaceEngineFactory(sdkLibPath, appId, sdkKey, engineConfiguration), poolConfig);

    }

    private int plusHundred(Float value) {
        BigDecimal target = new BigDecimal(value);
        BigDecimal hundred = new BigDecimal(100f);
        return target.multiply(hundred).intValue();

    }

    private class CompareFaceTask implements Callable<List<AdminFaceLoginVO>> {

        private List<AdminFaceLoginVO> AdminFaceLoginVOList;
        private FaceFeature targetFaceFeature;


        public CompareFaceTask(List<AdminFaceLoginVO> AdminFaceLoginVOList, FaceFeature targetFaceFeature) {
            this.AdminFaceLoginVOList = AdminFaceLoginVOList;
            this.targetFaceFeature = targetFaceFeature;
        }

        @Override
        public List<AdminFaceLoginVO> call() throws Exception {
            FaceEngine faceEngine = null;
            List<AdminFaceLoginVO> resultFaceInfoList = Lists.newLinkedList();//识别到的人脸列表
            try {
                faceEngine = faceEngineObjectPool.borrowObject();
                for (AdminFaceLoginVO AdminFaceLoginVO : AdminFaceLoginVOList) {
                    FaceFeature sourceFaceFeature = new FaceFeature();
                    sourceFaceFeature.setFeatureData(AdminFaceLoginVO.getFaceFeature());
                    FaceSimilar faceSimilar = new FaceSimilar();
                    faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
                    Integer similarValue = plusHundred(faceSimilar.getScore());//获取相似值
                    if (similarValue > passRate) {//相似值大于配置预期，加入到识别到人脸的列表

                        AdminFaceLoginVO info = new AdminFaceLoginVO();
                        info.setName(AdminFaceLoginVO.getName());
                        info.setId(AdminFaceLoginVO.getId());
                        info.setSimilarValue(similarValue);
                        resultFaceInfoList.add(info);
                    }
                }
            } catch (Exception e) {
                log.error("", e);
            } finally {
                if (faceEngine != null) {
                    faceEngineObjectPool.returnObject(faceEngine);
                }
            }

            return resultFaceInfoList;
        }

    }


}
